The purpose of this notebook is to create a neighborhood index that combines crime and cleanliness data. The index relies on 311 service request data and Part 1 violent crime data, both availble on Open Baltimore.

Brief Description of The Index

The index primarily relies on counts of violent crime and service requests per 1,000 residents. This is done to remove the influence of population on the counts of these events. The per-capita metrics are then scaled to between 0 and 1 to build up the overall index.

All Part 1 crime types are used.

The SR types used are:

Illegal Dumping:

Property Maintenance:

Right-of-Way Cleaning

knitr::opts_chunk$set(echo = T, 
                      warning = F, 
                      message = F,
                      include = T,
                      fig.width = 4,
                      fig.height = 3)
library(tidyverse)
library(ggiteam)
library(lubridate)
library(readxl)
library(spdplyr)
library(leaflet)
library(RSocrata)
library(geojsonsf)
library(ggmap)
library(rgdal)
library(scales)
library(sf)
library(htmltools)


register_google("AIzaSyByyKUj4QaAlzNtPJPw-HkEEr7d4hTawO0")
sr_query <- paste0("https://data.baltimorecity.gov/resource/9agw-sxsr.json",
                "?$where=",
                "((srtype like 'SW-Boarding' OR ",
                "srtype like 'SW-Cleaning' OR ",
                "srtype like 'SW-HGW' OR ",
                "srtype like 'SW-Dirty Alley' OR ",
                "srtype like 'SW-Dirty Street' OR ",
                "srtype like 'HCD-Illegal Dumping' OR ",
                "srtype like 'HCD-SIU' OR ",
                "srtype like 'SW-SIU Clean Up' OR ",
                "srtype like 'HCD-Sanitation Property') AND ",
                "date_extract_y(createddate)==2019)")

sr <- read.socrata(url = sr_query)

sr <- sr %>% 
  mutate(
    latitude = as.numeric(latitude), 
    longitude = as.numeric(longitude),
    neighborhood = toupper(neighborhood))
crime_query <- paste0("https://data.baltimorecity.gov/resource/wsfq-mvij.json?$where=",
                "(Description like 'HOMICIDE' OR ",
                "Description like 'SHOOTING' OR ", 
                "Description like 'AGG. ASSAULT' OR ",
                "Description like 'RAPE' OR ",
                "contains(Description, 'ROBBERY')) AND ",
                "date_extract_y(crimedate)==2019")

crime <- read.socrata(crime_query)
cityline_url <- "https://data.baltimorecity.gov/resource/nfyc-pejx.geojson"

# surely there is a better way, and i tried, but couldn't get anything else to work.
# from geojson to sf to spatial df. geojson_sp didn't work for me.
cityline <- geojson_sf(cityline_url)
cityline <- as_Spatial(cityline)
cityline <- spTransform(cityline, CRS("+init=epsg:4326"))
hoods.url <- "https://data.baltimorecity.gov/resource/h3fx-54q3.geojson"

# surely there is a better way, and i tried, but couldn't get anything else to work.
# from geojson to sf to spatial df. geojson_sp didn't work for me.
hoods <- geojson_sf(hoods.url)
hoods <- as_Spatial(hoods)
hoods <- spTransform(hoods, CRS("+init=epsg:4326"))
# join acs data to hoods
hoods_pop <- read_excel("../data/raw/Neighborhood Pop.xlsx")
hoods@data <- left_join(hoods@data, hoods_pop, by = c("label" = "Name"))
# bpd facilities to remove
facilities <- c(
  "300 E MADISON ST", # central booking
  "1400 E NORTH AVE", # district court
  "1900 ARGONNE DR", # northeastern
  "2200 W Cold Spring Ln", # northern
  "1000 N MOUNT ST", # western
  "5200 REISTERSTOWN RD ", # northwest
  "1600 EDISON HWY", # eastern
  "400 FONTHILL AVE", # southwest
  "0 CHERRY HILL RD", # southern
  "600 E FAYETTE ST", # central/hq
  "5700 EASTERN AVE", # southeast
  "2000 W BALTIMORE ST",  # bon secours
  "4000 DEEPWOOD RD", # loch raven va
  "300 N GAY ST", # juv booking
  "4900 EASTERN AV", # bayview
  "1800 ORLEANS ST", # jhh downtown
  "600 N WOLFE ST", # jhh downtown
  "0 S GREENE ST", # umd medical center
  "3400 N CALVERT ST" # union memorial
)

Population distribution of Baltimore neighborhoods.

hoods@data %>%
  ggplot(aes(ACS_2013_2017)) +
  geom_histogram() +
  theme_iteam_google_docs()

Tenth percentile for population:

quantile(hoods$ACS_2013_2017, 0.1, na.rm = T)
     10% 
174.7548 

Analysis

crime_labeled <- crime %>%
  filter(
    !is.na(latitude),
    !(location %in% facilities)
    ) %>%
  st_as_sf(coords = c("longitude", "latitude"), crs = 4326) %>%
  st_join(st_as_sf(hoods), join = st_within, left = F)

crime_hood_counts <- crime_labeled %>%
  data.frame() %>%
  count(description, label) %>%
  spread(key = description, value = n) %>%
  replace(is.na(.), 0) %>%
  transmute(
    label = label,
    agg_assault = `AGG. ASSAULT`,
    rape = RAPE,
    homicide_shootings = HOMICIDE + SHOOTING,
    robbery = `ROBBERY - COMMERCIAL` +
      `ROBBERY - CARJACKING` +
      `ROBBERY - RESIDENCE` +
      `ROBBERY - STREET`) 
sr_labeled <- sr %>%
  filter(
    !is.na(latitude)
    ) %>%
  st_as_sf(coords = c("longitude", "latitude"), crs = 4326) %>%
  st_join(st_as_sf(hoods), join = st_within, left = F)

sr_hood_counts <- sr_labeled %>%
  data.frame() %>%
  count(srtype, label) %>%
  spread(key = srtype, value = n) %>%
  replace(is.na(.), 0) %>%
  transmute(
    label = label,
    illegal_dumping = `HCD-Illegal Dumping` + `SW-SIU Clean Up`,
    property_maintenance = `HCD-Sanitation Property` +
      `SW-Boarding` +
      `SW-Cleaning` +
      `SW-HGW`,
    right_of_way = `SW-Dirty Street` + `SW-Dirty Alley`
  )
# to per 1,000 residents
to_per_capita <- function(x){1000 * x / hoods$ACS_2013_2017}

hood_metrics <- hoods

hood_metrics@data <- hood_metrics@data %>%
  select(-acres,
         -nbrdesc,
         -shape_leng,
         -shape_area,
         -color_2,
         -Decennial2010)

hood_metrics@data <- hood_metrics@data %>%
  left_join(
    crime_hood_counts,
    by = "label") %>%
  left_join(
    sr_hood_counts,
    by = "label"
  ) %>%
  mutate_at(
    vars(
      illegal_dumping,
      property_maintenance,
      right_of_way,
      homicide_shootings,
      robbery,
      agg_assault,
      rape),
    list(per_1000_res = to_per_capita))

Distribution of per capita rates of crime and service requests:

hood_metrics@data %>%
  select(
    label,
    illegal_dumping_per_1000_res,
    property_maintenance_per_1000_res,
    right_of_way_per_1000_res,
    homicide_shootings_per_1000_res,
    robbery_per_1000_res,
    agg_assault_per_1000_res,
    rape_per_1000_res
  ) %>%
  pivot_longer(-label) %>%
  ggplot(aes(value)) +
  facet_wrap(~name, scales = "free") +
  geom_histogram(bins = 50) +
  theme_iteam_google_docs() +
  labs(title = "2019 Events per 1,000 Residents")

In all cases, outlier neighborhoods with very small populations skew data.

Try removing neighborhoods with less than 150 residents (10% of neighborhoods). These are typically parks, industrial areas, etc.

hood_metrics@data %>%
  filter(ACS_2013_2017 >= 150) %>%
  select(
    label,
    illegal_dumping_per_1000_res,
    property_maintenance_per_1000_res,
    right_of_way_per_1000_res,
    homicide_shootings_per_1000_res,
    robbery_per_1000_res,
    agg_assault_per_1000_res,
    rape_per_1000_res
  ) %>%
  pivot_longer(-label) %>%
  ggplot(aes(value)) +
  facet_wrap(~name, scales = "free") +
  geom_histogram(bins = 50) +
    theme_iteam_google_docs() +
  labs(title = "2019 Events per 1,000 Residents\nNeighborhoods with >= 150 Residents")

Going forward, removing neighborhoods with less than 150 residents.

hood_metrics_over150 <- subset(hood_metrics, hood_metrics$ACS_2013_2017 > 150)
hood_metrics_over150 <- subset(hood_metrics_over150, !grepl("INDUSTRIAL", label, ignore.case = T))

Number of neighborhoods with at least one homicide or shooting:

hood_metrics_over150@data %>%
  count(homicide_shootings >= 1)

Will likely assign 1 to neighborhoods with at least 1 homicide or shooting, 0 to all others. This will ensure neighborhoods with gun violence are given precedence.

Building The Index

All parameters will be scaled to between 0 and 1 because variables are on different scales.

# scale per capita
scale_index <- function(x){rescale(x, to = c(0,1))}

hood_metrics_over150@data <- hood_metrics_over150@data %>%
  mutate(
    homicide_shootings_index = ifelse(homicide_shootings > 0, 1, 0),
    rape_index = ifelse(rape > 0, 1, 0)) %>%
  mutate_at(
    vars(
      illegal_dumping_per_1000_res,
      property_maintenance_per_1000_res,
      right_of_way_per_1000_res,
      homicide_shootings_per_1000_res,
      robbery_per_1000_res,
      agg_assault_per_1000_res,
      rape_per_1000_res),
    list(index = scale_index)
    ) 
# plot rescaled data
hood_metrics_over150@data %>%
  select(
    label,
    illegal_dumping_per_1000_res_index,
    property_maintenance_per_1000_res_index,
    right_of_way_per_1000_res_index,
    homicide_shootings_per_1000_res_index,
    robbery_per_1000_res_index,
    agg_assault_per_1000_res_index,
    rape_per_1000_res_index
  ) %>%
  pivot_longer(-label) %>%
  ggplot(aes(value)) +
  facet_wrap(~name, scales = "free") +
  geom_histogram(bins = 50) +
    theme_iteam_google_docs() +
  labs(title = "Standardized (between 0 and 1) 2019 Events per 1,000 Residents\nNeighborhoods with >= 150 Residents")

Again, all metrics have been scaled to between 0 and 1. Combine crime metrics into a homicide and shooting parameter (0 if none in neighborhood, 1 if at least 1) and an “other violent crime” parameter where:

\[ HomicideShootingsIndex = \begin{cases} 1 & \text{if $N_{homicides}+N_{shootings} > 0$ } \\ 0 & \text{otherwise} \end{cases}\]

\[ OtherViolentCrimeIndex = \frac{1}{3}(RapeIndex+ AggAssaultIndex + RobberyIndex) \]

Combining the two crime indices:

\[ CrimeIndex = \frac{1}{2} (HomicideShootingsIndex + OtherViolentCrimeIndex) \]

# create combined index
hood_metrics_over150@data <- hood_metrics_over150@data %>%
  rowwise() %>%
  mutate(
    other_violent_crime_index = 
      mean(
        c(rape_per_1000_res_index,
          agg_assault_per_1000_res_index, 
          robbery_per_1000_res_index), 
        na.rm = T),
    grime_index = mean(
      c(illegal_dumping_per_1000_res_index,
        property_maintenance_per_1000_res_index,
        right_of_way_per_1000_res_index),
      na.rm = T)
  ) %>%
  ungroup() %>%
  mutate(crime_index = 0.5 * homicide_shootings_per_1000_res_index + 
           0.5 * other_violent_crime_index,
         crime_grime_index = (2/3) * crime_index + (1/3) * grime_index,
         crime_grime_index_quadrature = sqrt(crime_index^2 + grime_index^2))

Now creating the “grime” index:

\[ GrimeIndex = \frac{1}{3}(IllegalDumpingIndex + PropMaintenanceIndex + RightOfWayIndex) \] Combine into a single index. Crime weighted more heavily.

\[ CrimeGrimeIndex = \frac{2}{3}CrimeIndex + \frac{1}{3}GrimeIndex \]

Just Show Me The Results

# base_map <- get_map(
#   location=c(lon = -76.61, lat = 39.3), 
#   zoom=12, 
#   maptype = 'toner-lite', 
#   source = 'stamen'
#   )
# 
# ggmap(base_map) +
#   # geom_point(
#   #   data = repeat_offender_locations %>%
#   #     filter(!is.na(lon),
#   #            n >= 3),
#   #   aes(x = lon, y = lat),
#   #   color = "blue",
#   #   size = 2
#   # )  
#   geom_polygon(
#     data = fortify(hood_metrics_over150) %>%
#       filter(!is.na(crime_grime_index)),
#     aes(x = long, y = lat, group = group, fill = crime_grime_index), 
#     alpha = 0.9,color = "black"
#   )# +
#   # scale_fill_viridis_c(na.value = NA, 
#   #                      #values = rescale(c(0, 20, 70), c(0,1)),
#   #                      option = "plasma",
#   #                      guide_colorbar(title = "Annual DV Incidents\nper 1,000 Residents"))
#   # 
hoods@data <- hoods@data %>%
  left_join(
    hood_metrics_over150@data %>% select(-ACS_2013_2017),
    by = "label"
  )
# color palette for map
pal <-  colorNumeric("viridis", 
                     domain = hoods$crime_grime_index,
                     na.color = "transparent")

# labels
hoods@data <- hoods@data %>%
  mutate(map_label = paste0(
    "<b>",hoods$label, "</b><br>",
    "Population: ", round(hoods$ACS_2013_2017, 0), "<br>",
    "Crime & Grime Index: ", round(hoods$crime_grime_index, 2), "<br>",
    "Crime Index: ", round(hoods$crime_index, 2), "<br>",
    "Grime Index: ", round(hoods$grime_index, 2), "<br>",
    "<br><b>2019 Crime</b><br>",
    "Homicides + Shootings: ", hoods$homicide_shootings, "<br>",
    "Rape: ", hoods$rape, "<br>",
    "Aggravated Assault: ", hoods$agg_assault, "<br>",
    "Robbery: ", hoods$robbery, "<br>",
    "<br><b>2019 Service Requests</b><br>",
    "Illegal Dumping SRs: ", hoods$illegal_dumping, "<br>",
    "Property Maintenance SRs: ", hoods$property_maintenance, "<br>",
    "Right-of-Way SRs: ", hoods$right_of_way, "<br>"
))
leaflet() %>%
  setView(lng = -76.6, lat = 39.3, zoom = 11) %>%
  addProviderTiles(providers$Stamen.TonerLite) %>% 
  addPolygons(data = hoods,
              weight = 2,
              fillColor  = ~pal(hoods$crime_grime_index),
              color = "black",
              opacity = 0.9,
              fillOpacity = 0.75,
              label = hoods$label,
              popup = ~lapply(hoods$map_label, HTML),
              labelOptions = labelOptions(noHide = F, 
                                          #textOnly = T,
                                          direction = 'right',
                                          textsize = 2,
                                          opacity = 1)) %>%
  addControl("Baltimore<br>Crime & Grime Index", position = "topleft")

Distribution of Indexes

Because of the homicide and shooting index being 1 for neighborhoods with at least one homicide or shooting and 0 for all others, the distribution is bifurcated into neighborhoods with and without shootings.

hoods@data %>%
  ggplot(aes(crime_grime_index)) +
  geom_histogram() +
  theme_iteam_google_docs() 

The List, In Order of Descending Crime-Grime Index

Some neighborhoods with very small populations still sneak onto the list because we only used 150 as the cut-off, but those can be manually dropped if not interested.

the_list <- hoods@data %>%
  arrange(desc(crime_grime_index)) %>%
  select(-acres, -nbrdesc, -shape_leng, -shape_area, -color_2, -Decennial2010) %>%
  rename(neighborhood = label,
         population_acs_2013_2017 = ACS_2013_2017)

write_csv(the_list, "../data/processed/crime_grime_index_v3.csv")

the_list
top_20 <- hoods %>%
  arrange(desc(crime_grime_index)) %>%
  top_n(n = 20, wt = crime_grime_index)
color_list <- hoods@data %>%
  arrange(desc(crime_grime_index)) %>%
  mutate(top20_color = ifelse(row_number() <= 20, iteam.colors[1], NA)) %>%
  select(label, top20_color)

hoods@data <- hoods@data %>%
  left_join(color_list, by = "label")

Top 20 Neighborhoods

fill_pal <- colorFactor(hoods$top20_color, hoods$top20_color, na.color = "transparent")

leaflet() %>%
  setView(lng = -76.6, lat = 39.3, zoom = 11) %>%
  addProviderTiles(providers$Stamen.TonerLite) %>% 
  addPolygons(data = hoods,
              weight = 2,
              fillColor  = ~fill_pal(hoods$top20_color),
              color = "black",
              opacity = 0.9,
              fillOpacity = 0.75,
              label = hoods$label,
              popup = ~lapply(hoods$map_label, HTML),
              labelOptions = labelOptions(noHide = F, 
                                          #textOnly = T,
                                          direction = 'right',
                                          textsize = 2,
                                          opacity = 1)) %>%
  addControl("Baltimore<br>Crime & Grime Index<br>Top 20 Neighborhoods<br><br>Click Neighborhood<br>For More Info", 
             position = "topleft")

Crime v. Grime

Red means the crime index was higher, blue means the grime index was higher.

hoods@data <- hoods@data %>% mutate(crime_grime_index_diff = crime_index - grime_index)
diff_pal <-  colorNumeric("RdBu", 
                     domain = hoods$crime_grime_index_diff,
                     na.color = "transparent", reverse = T)

leaflet() %>%
  setView(lng = -76.6, lat = 39.3, zoom = 11) %>%
  addProviderTiles(providers$Stamen.TonerLite) %>% 
  addPolygons(data = hoods,
              weight = 2,
              fillColor  = ~diff_pal(hoods$crime_grime_index_diff),
              color = "black",
              opacity = 0.9,
              fillOpacity = 0.75,
              label = hoods$label,
              popup = ~lapply(hoods$map_label, HTML),
              labelOptions = labelOptions(noHide = F, 
                                          #textOnly = T,
                                          direction = 'right',
                                          textsize = 2,
                                          opacity = 1)) %>%
  addControl("Baltimore<br>Crime & Grime Index<br>Crime-Grime Difference<br><br>Click Neighborhood<br>For More Info", 
             position = "topleft") %>%
  addLegend(pal = diff_pal, values = hoods$crime_grime_index_diff, position = "bottomright", title = "Red: More Crime<br>Blue: More Grime")
LS0tCnRpdGxlOiAiQ3JpbWUgKyBHcmltZSBJbmRleCBEZXZlbG9wbWVudCIKYXV0aG9yOiAiSnVzdGluIEVsc3phc3oiCmVtYWlsOiAianVzdGluLmVsc3phc3pAYmFsdGltb3JlY2l0eS5nb3YiCmRhdGU6ICJUdWVzZGF5LCBKYW51YXJ5IDIxLCAyMDIwIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgZmlnX2hlaWdodDogNQogICAgZmlnX3dpZHRoOiAxMAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMQplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KClRoZSBwdXJwb3NlIG9mIHRoaXMgbm90ZWJvb2sgaXMgdG8gY3JlYXRlIGEgbmVpZ2hib3Job29kIGluZGV4IHRoYXQgY29tYmluZXMgY3JpbWUgYW5kIGNsZWFubGluZXNzIGRhdGEuIFRoZSBpbmRleCByZWxpZXMgb24gWzMxMSBzZXJ2aWNlIHJlcXVlc3QgZGF0YV0oaHR0cHM6Ly9kYXRhLmJhbHRpbW9yZWNpdHkuZ292L0NpdHktU2VydmljZXMvMzExLUN1c3RvbWVyLVNlcnZpY2UtUmVxdWVzdHMvOWFndy1zeHNyKSBhbmQgW1BhcnQgMSB2aW9sZW50IGNyaW1lIGRhdGFdKGh0dHBzOi8vZGF0YS5iYWx0aW1vcmVjaXR5Lmdvdi9QdWJsaWMtU2FmZXR5L0JQRC1QYXJ0LTEtVmljdGltLUJhc2VkLUNyaW1lLURhdGEvd3NmcS1tdmlqKSwgYm90aCBhdmFpbGJsZSBvbiBPcGVuIEJhbHRpbW9yZS4KCiMgQnJpZWYgRGVzY3JpcHRpb24gb2YgVGhlIEluZGV4CgpUaGUgaW5kZXggcHJpbWFyaWx5IHJlbGllcyBvbiBjb3VudHMgb2YgdmlvbGVudCBjcmltZSBhbmQgc2VydmljZSByZXF1ZXN0cyAqcGVyIDEsMDAwIHJlc2lkZW50cyouIFRoaXMgaXMgZG9uZSB0byByZW1vdmUgdGhlIGluZmx1ZW5jZSBvZiBwb3B1bGF0aW9uIG9uIHRoZSBjb3VudHMgb2YgdGhlc2UgZXZlbnRzLiBUaGUgcGVyLWNhcGl0YSBtZXRyaWNzIGFyZSB0aGVuIHNjYWxlZCB0byBiZXR3ZWVuIDAgYW5kIDEgdG8gYnVpbGQgdXAgdGhlIG92ZXJhbGwgaW5kZXguCgpBbGwgUGFydCAxIGNyaW1lIHR5cGVzIGFyZSB1c2VkLgoKVGhlIFNSIHR5cGVzIHVzZWQgYXJlOgoKSWxsZWdhbCBEdW1waW5nOgoKLSBIQ0QtSWxsZWdhbCBEdW1waW5nCi0gU1ctU0lVIENsZWFuIFVwCgpQcm9wZXJ0eSBNYWludGVuYW5jZToKCi0gSENELVNhbml0YXRpb24gUHJvcGVydHkKLSBTVy1Cb2FyZGluZwotIFNXLUhHVwotIFNXLUNsZWFuaW5nCgpSaWdodC1vZi1XYXkgQ2xlYW5pbmcKCi0gU1ctRGlydHkgU3RyZWV0Ci0gU1ctRGlydHkgQWxsZXkKCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IFQsIGVjaG8gPSBULCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBULCAKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGLCAKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGLAogICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZSA9IFQsCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSA0LAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDMpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ2l0ZWFtKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoc3BkcGx5cikKbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KFJTb2NyYXRhKQpsaWJyYXJ5KGdlb2pzb25zZikKbGlicmFyeShnZ21hcCkKbGlicmFyeShyZ2RhbCkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoc2YpCmxpYnJhcnkoaHRtbHRvb2xzKQoKCnJlZ2lzdGVyX2dvb2dsZSgiQUl6YVN5Qnl5S1VqNFFhQWx6TnRQSlB3LUhrRUVyN2Q0aFRhd08wIikKYGBgCiAKYGBge3IgbG9hZF9zciwgY2FjaGUgPSBUfQpzcl9xdWVyeSA8LSBwYXN0ZTAoImh0dHBzOi8vZGF0YS5iYWx0aW1vcmVjaXR5Lmdvdi9yZXNvdXJjZS85YWd3LXN4c3IuanNvbiIsCiAgICAgICAgICAgICAgICAiPyR3aGVyZT0iLAogICAgICAgICAgICAgICAgIigoc3J0eXBlIGxpa2UgJ1NXLUJvYXJkaW5nJyBPUiAiLAogICAgICAgICAgICAgICAgInNydHlwZSBsaWtlICdTVy1DbGVhbmluZycgT1IgIiwKICAgICAgICAgICAgICAgICJzcnR5cGUgbGlrZSAnU1ctSEdXJyBPUiAiLAogICAgICAgICAgICAgICAgInNydHlwZSBsaWtlICdTVy1EaXJ0eSBBbGxleScgT1IgIiwKICAgICAgICAgICAgICAgICJzcnR5cGUgbGlrZSAnU1ctRGlydHkgU3RyZWV0JyBPUiAiLAogICAgICAgICAgICAgICAgInNydHlwZSBsaWtlICdIQ0QtSWxsZWdhbCBEdW1waW5nJyBPUiAiLAogICAgICAgICAgICAgICAgInNydHlwZSBsaWtlICdIQ0QtU0lVJyBPUiAiLAogICAgICAgICAgICAgICAgInNydHlwZSBsaWtlICdTVy1TSVUgQ2xlYW4gVXAnIE9SICIsCiAgICAgICAgICAgICAgICAic3J0eXBlIGxpa2UgJ0hDRC1TYW5pdGF0aW9uIFByb3BlcnR5JykgQU5EICIsCiAgICAgICAgICAgICAgICAiZGF0ZV9leHRyYWN0X3koY3JlYXRlZGRhdGUpPT0yMDE5KSIpCgpzciA8LSByZWFkLnNvY3JhdGEodXJsID0gc3JfcXVlcnkpCgpzciA8LSBzciAlPiUgCiAgbXV0YXRlKAogICAgbGF0aXR1ZGUgPSBhcy5udW1lcmljKGxhdGl0dWRlKSwgCiAgICBsb25naXR1ZGUgPSBhcy5udW1lcmljKGxvbmdpdHVkZSksCiAgICBuZWlnaGJvcmhvb2QgPSB0b3VwcGVyKG5laWdoYm9yaG9vZCkpCmBgYAoKYGBge3IgbG9hZF9jcmltZSwgY2FjaGUgPSBUfQpjcmltZV9xdWVyeSA8LSBwYXN0ZTAoImh0dHBzOi8vZGF0YS5iYWx0aW1vcmVjaXR5Lmdvdi9yZXNvdXJjZS93c2ZxLW12aWouanNvbj8kd2hlcmU9IiwKICAgICAgICAgICAgICAgICIoRGVzY3JpcHRpb24gbGlrZSAnSE9NSUNJREUnIE9SICIsCiAgICAgICAgICAgICAgICAiRGVzY3JpcHRpb24gbGlrZSAnU0hPT1RJTkcnIE9SICIsIAogICAgICAgICAgICAgICAgIkRlc2NyaXB0aW9uIGxpa2UgJ0FHRy4gQVNTQVVMVCcgT1IgIiwKICAgICAgICAgICAgICAgICJEZXNjcmlwdGlvbiBsaWtlICdSQVBFJyBPUiAiLAogICAgICAgICAgICAgICAgImNvbnRhaW5zKERlc2NyaXB0aW9uLCAnUk9CQkVSWScpKSBBTkQgIiwKICAgICAgICAgICAgICAgICJkYXRlX2V4dHJhY3RfeShjcmltZWRhdGUpPT0yMDE5IikKCmNyaW1lIDwtIHJlYWQuc29jcmF0YShjcmltZV9xdWVyeSkKYGBgCgpgYGB7ciBjaXR5bGluZX0KY2l0eWxpbmVfdXJsIDwtICJodHRwczovL2RhdGEuYmFsdGltb3JlY2l0eS5nb3YvcmVzb3VyY2UvbmZ5Yy1wZWp4Lmdlb2pzb24iCgojIHN1cmVseSB0aGVyZSBpcyBhIGJldHRlciB3YXksIGFuZCBpIHRyaWVkLCBidXQgY291bGRuJ3QgZ2V0IGFueXRoaW5nIGVsc2UgdG8gd29yay4KIyBmcm9tIGdlb2pzb24gdG8gc2YgdG8gc3BhdGlhbCBkZi4gZ2VvanNvbl9zcCBkaWRuJ3Qgd29yayBmb3IgbWUuCmNpdHlsaW5lIDwtIGdlb2pzb25fc2YoY2l0eWxpbmVfdXJsKQpjaXR5bGluZSA8LSBhc19TcGF0aWFsKGNpdHlsaW5lKQpjaXR5bGluZSA8LSBzcFRyYW5zZm9ybShjaXR5bGluZSwgQ1JTKCIraW5pdD1lcHNnOjQzMjYiKSkKCmBgYAoKCmBgYHtyIGxvYWRfbmVpZ2hib3Job29kc30KaG9vZHMudXJsIDwtICJodHRwczovL2RhdGEuYmFsdGltb3JlY2l0eS5nb3YvcmVzb3VyY2UvaDNmeC01NHEzLmdlb2pzb24iCgojIHN1cmVseSB0aGVyZSBpcyBhIGJldHRlciB3YXksIGFuZCBpIHRyaWVkLCBidXQgY291bGRuJ3QgZ2V0IGFueXRoaW5nIGVsc2UgdG8gd29yay4KIyBmcm9tIGdlb2pzb24gdG8gc2YgdG8gc3BhdGlhbCBkZi4gZ2VvanNvbl9zcCBkaWRuJ3Qgd29yayBmb3IgbWUuCmhvb2RzIDwtIGdlb2pzb25fc2YoaG9vZHMudXJsKQpob29kcyA8LSBhc19TcGF0aWFsKGhvb2RzKQpob29kcyA8LSBzcFRyYW5zZm9ybShob29kcywgQ1JTKCIraW5pdD1lcHNnOjQzMjYiKSkKYGBgCgpgYGB7cn0KIyBqb2luIGFjcyBkYXRhIHRvIGhvb2RzCmhvb2RzX3BvcCA8LSByZWFkX2V4Y2VsKCIuLi9kYXRhL3Jhdy9OZWlnaGJvcmhvb2QgUG9wLnhsc3giKQpob29kc0BkYXRhIDwtIGxlZnRfam9pbihob29kc0BkYXRhLCBob29kc19wb3AsIGJ5ID0gYygibGFiZWwiID0gIk5hbWUiKSkKYGBgCgoKYGBge3J9CiMgYnBkIGZhY2lsaXRpZXMgdG8gcmVtb3ZlCmZhY2lsaXRpZXMgPC0gYygKICAiMzAwIEUgTUFESVNPTiBTVCIsICMgY2VudHJhbCBib29raW5nCiAgIjE0MDAgRSBOT1JUSCBBVkUiLCAjIGRpc3RyaWN0IGNvdXJ0CiAgIjE5MDAgQVJHT05ORSBEUiIsICMgbm9ydGhlYXN0ZXJuCiAgIjIyMDAgVyBDb2xkIFNwcmluZyBMbiIsICMgbm9ydGhlcm4KICAiMTAwMCBOIE1PVU5UIFNUIiwgIyB3ZXN0ZXJuCiAgIjUyMDAgUkVJU1RFUlNUT1dOIFJECSIsICMgbm9ydGh3ZXN0CiAgIjE2MDAgRURJU09OIEhXWSIsICMgZWFzdGVybgogICI0MDAgRk9OVEhJTEwgQVZFIiwgIyBzb3V0aHdlc3QKICAiMCBDSEVSUlkgSElMTCBSRCIsICMgc291dGhlcm4KICAiNjAwIEUgRkFZRVRURSBTVCIsICMgY2VudHJhbC9ocQogICI1NzAwIEVBU1RFUk4gQVZFIiwgIyBzb3V0aGVhc3QKICAiMjAwMCBXIEJBTFRJTU9SRSBTVCIsICAjIGJvbiBzZWNvdXJzCiAgIjQwMDAgREVFUFdPT0QgUkQiLCAjIGxvY2ggcmF2ZW4gdmEKICAiMzAwIE4gR0FZIFNUIiwgIyBqdXYgYm9va2luZwogICI0OTAwIEVBU1RFUk4gQVYiLCAjIGJheXZpZXcKICAiMTgwMCBPUkxFQU5TIFNUIiwgIyBqaGggZG93bnRvd24KICAiNjAwIE4gV09MRkUgU1QiLCAjIGpoaCBkb3dudG93bgogICIwIFMgR1JFRU5FIFNUIiwgIyB1bWQgbWVkaWNhbCBjZW50ZXIKICAiMzQwMCBOIENBTFZFUlQgU1QiICMgdW5pb24gbWVtb3JpYWwKKQoKYGBgCgpQb3B1bGF0aW9uIGRpc3RyaWJ1dGlvbiBvZiBCYWx0aW1vcmUgbmVpZ2hib3Job29kcy4KCmBgYHtyIGZpZy5oZWlnaHQgPSAxLCBmaWcud2lkdGggPSAyfQpob29kc0BkYXRhICU+JQogIGdncGxvdChhZXMoQUNTXzIwMTNfMjAxNykpICsKICBnZW9tX2hpc3RvZ3JhbSgpICsKICB0aGVtZV9pdGVhbV9nb29nbGVfZG9jcygpCmBgYAoKVGVudGggcGVyY2VudGlsZSBmb3IgcG9wdWxhdGlvbjoKCmBgYHtyfQpxdWFudGlsZShob29kcyRBQ1NfMjAxM18yMDE3LCAwLjEsIG5hLnJtID0gVCkKYGBgCgojIEFuYWx5c2lzCgpgYGB7cn0KY3JpbWVfbGFiZWxlZCA8LSBjcmltZSAlPiUKICBmaWx0ZXIoCiAgICAhaXMubmEobGF0aXR1ZGUpLAogICAgIShsb2NhdGlvbiAlaW4lIGZhY2lsaXRpZXMpCiAgICApICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoImxvbmdpdHVkZSIsICJsYXRpdHVkZSIpLCBjcnMgPSA0MzI2KSAlPiUKICBzdF9qb2luKHN0X2FzX3NmKGhvb2RzKSwgam9pbiA9IHN0X3dpdGhpbiwgbGVmdCA9IEYpCgpjcmltZV9ob29kX2NvdW50cyA8LSBjcmltZV9sYWJlbGVkICU+JQogIGRhdGEuZnJhbWUoKSAlPiUKICBjb3VudChkZXNjcmlwdGlvbiwgbGFiZWwpICU+JQogIHNwcmVhZChrZXkgPSBkZXNjcmlwdGlvbiwgdmFsdWUgPSBuKSAlPiUKICByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUKICB0cmFuc211dGUoCiAgICBsYWJlbCA9IGxhYmVsLAogICAgYWdnX2Fzc2F1bHQgPSBgQUdHLiBBU1NBVUxUYCwKICAgIHJhcGUgPSBSQVBFLAogICAgaG9taWNpZGVfc2hvb3RpbmdzID0gSE9NSUNJREUgKyBTSE9PVElORywKICAgIHJvYmJlcnkgPSBgUk9CQkVSWSAtIENPTU1FUkNJQUxgICsKICAgICAgYFJPQkJFUlkgLSBDQVJKQUNLSU5HYCArCiAgICAgIGBST0JCRVJZIC0gUkVTSURFTkNFYCArCiAgICAgIGBST0JCRVJZIC0gU1RSRUVUYCkgCmBgYAoKYGBge3J9CnNyX2xhYmVsZWQgPC0gc3IgJT4lCiAgZmlsdGVyKAogICAgIWlzLm5hKGxhdGl0dWRlKQogICAgKSAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJsb25naXR1ZGUiLCAibGF0aXR1ZGUiKSwgY3JzID0gNDMyNikgJT4lCiAgc3Rfam9pbihzdF9hc19zZihob29kcyksIGpvaW4gPSBzdF93aXRoaW4sIGxlZnQgPSBGKQoKc3JfaG9vZF9jb3VudHMgPC0gc3JfbGFiZWxlZCAlPiUKICBkYXRhLmZyYW1lKCkgJT4lCiAgY291bnQoc3J0eXBlLCBsYWJlbCkgJT4lCiAgc3ByZWFkKGtleSA9IHNydHlwZSwgdmFsdWUgPSBuKSAlPiUKICByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUKICB0cmFuc211dGUoCiAgICBsYWJlbCA9IGxhYmVsLAogICAgaWxsZWdhbF9kdW1waW5nID0gYEhDRC1JbGxlZ2FsIER1bXBpbmdgICsgYFNXLVNJVSBDbGVhbiBVcGAsCiAgICBwcm9wZXJ0eV9tYWludGVuYW5jZSA9IGBIQ0QtU2FuaXRhdGlvbiBQcm9wZXJ0eWAgKwogICAgICBgU1ctQm9hcmRpbmdgICsKICAgICAgYFNXLUNsZWFuaW5nYCArCiAgICAgIGBTVy1IR1dgLAogICAgcmlnaHRfb2Zfd2F5ID0gYFNXLURpcnR5IFN0cmVldGAgKyBgU1ctRGlydHkgQWxsZXlgCiAgKQoKYGBgCgpgYGB7cn0KIyB0byBwZXIgMSwwMDAgcmVzaWRlbnRzCnRvX3Blcl9jYXBpdGEgPC0gZnVuY3Rpb24oeCl7MTAwMCAqIHggLyBob29kcyRBQ1NfMjAxM18yMDE3fQoKaG9vZF9tZXRyaWNzIDwtIGhvb2RzCgpob29kX21ldHJpY3NAZGF0YSA8LSBob29kX21ldHJpY3NAZGF0YSAlPiUKICBzZWxlY3QoLWFjcmVzLAogICAgICAgICAtbmJyZGVzYywKICAgICAgICAgLXNoYXBlX2xlbmcsCiAgICAgICAgIC1zaGFwZV9hcmVhLAogICAgICAgICAtY29sb3JfMiwKICAgICAgICAgLURlY2VubmlhbDIwMTApCgpob29kX21ldHJpY3NAZGF0YSA8LSBob29kX21ldHJpY3NAZGF0YSAlPiUKICBsZWZ0X2pvaW4oCiAgICBjcmltZV9ob29kX2NvdW50cywKICAgIGJ5ID0gImxhYmVsIikgJT4lCiAgbGVmdF9qb2luKAogICAgc3JfaG9vZF9jb3VudHMsCiAgICBieSA9ICJsYWJlbCIKICApICU+JQogIG11dGF0ZV9hdCgKICAgIHZhcnMoCiAgICAgIGlsbGVnYWxfZHVtcGluZywKICAgICAgcHJvcGVydHlfbWFpbnRlbmFuY2UsCiAgICAgIHJpZ2h0X29mX3dheSwKICAgICAgaG9taWNpZGVfc2hvb3RpbmdzLAogICAgICByb2JiZXJ5LAogICAgICBhZ2dfYXNzYXVsdCwKICAgICAgcmFwZSksCiAgICBsaXN0KHBlcl8xMDAwX3JlcyA9IHRvX3Blcl9jYXBpdGEpKQpgYGAKCkRpc3RyaWJ1dGlvbiBvZiBwZXIgY2FwaXRhIHJhdGVzIG9mIGNyaW1lIGFuZCBzZXJ2aWNlIHJlcXVlc3RzOgoKYGBge3J9Cmhvb2RfbWV0cmljc0BkYXRhICU+JQogIHNlbGVjdCgKICAgIGxhYmVsLAogICAgaWxsZWdhbF9kdW1waW5nX3Blcl8xMDAwX3JlcywKICAgIHByb3BlcnR5X21haW50ZW5hbmNlX3Blcl8xMDAwX3JlcywKICAgIHJpZ2h0X29mX3dheV9wZXJfMTAwMF9yZXMsCiAgICBob21pY2lkZV9zaG9vdGluZ3NfcGVyXzEwMDBfcmVzLAogICAgcm9iYmVyeV9wZXJfMTAwMF9yZXMsCiAgICBhZ2dfYXNzYXVsdF9wZXJfMTAwMF9yZXMsCiAgICByYXBlX3Blcl8xMDAwX3JlcwogICkgJT4lCiAgcGl2b3RfbG9uZ2VyKC1sYWJlbCkgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSkpICsKICBmYWNldF93cmFwKH5uYW1lLCBzY2FsZXMgPSAiZnJlZSIpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNTApICsKICB0aGVtZV9pdGVhbV9nb29nbGVfZG9jcygpICsKICBsYWJzKHRpdGxlID0gIjIwMTkgRXZlbnRzIHBlciAxLDAwMCBSZXNpZGVudHMiKQpgYGAKCkluIGFsbCBjYXNlcywgb3V0bGllciBuZWlnaGJvcmhvb2RzIHdpdGggdmVyeSBzbWFsbCBwb3B1bGF0aW9ucyBza2V3IGRhdGEuIAoKVHJ5IHJlbW92aW5nIG5laWdoYm9yaG9vZHMgd2l0aCBsZXNzIHRoYW4gMTUwIHJlc2lkZW50cyAoMTAlIG9mIG5laWdoYm9yaG9vZHMpLiBUaGVzZSBhcmUgdHlwaWNhbGx5IHBhcmtzLCBpbmR1c3RyaWFsIGFyZWFzLCBldGMuCgpgYGB7cn0KaG9vZF9tZXRyaWNzQGRhdGEgJT4lCiAgZmlsdGVyKEFDU18yMDEzXzIwMTcgPj0gMTUwKSAlPiUKICBzZWxlY3QoCiAgICBsYWJlbCwKICAgIGlsbGVnYWxfZHVtcGluZ19wZXJfMTAwMF9yZXMsCiAgICBwcm9wZXJ0eV9tYWludGVuYW5jZV9wZXJfMTAwMF9yZXMsCiAgICByaWdodF9vZl93YXlfcGVyXzEwMDBfcmVzLAogICAgaG9taWNpZGVfc2hvb3RpbmdzX3Blcl8xMDAwX3JlcywKICAgIHJvYmJlcnlfcGVyXzEwMDBfcmVzLAogICAgYWdnX2Fzc2F1bHRfcGVyXzEwMDBfcmVzLAogICAgcmFwZV9wZXJfMTAwMF9yZXMKICApICU+JQogIHBpdm90X2xvbmdlcigtbGFiZWwpICU+JQogIGdncGxvdChhZXModmFsdWUpKSArCiAgZmFjZXRfd3JhcCh+bmFtZSwgc2NhbGVzID0gImZyZWUiKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDUwKSArCiAgICB0aGVtZV9pdGVhbV9nb29nbGVfZG9jcygpICsKICBsYWJzKHRpdGxlID0gIjIwMTkgRXZlbnRzIHBlciAxLDAwMCBSZXNpZGVudHNcbk5laWdoYm9yaG9vZHMgd2l0aCA+PSAxNTAgUmVzaWRlbnRzIikKYGBgCgpHb2luZyBmb3J3YXJkLCByZW1vdmluZyBuZWlnaGJvcmhvb2RzIHdpdGggbGVzcyB0aGFuIDE1MCByZXNpZGVudHMuCgpgYGB7cn0KaG9vZF9tZXRyaWNzX292ZXIxNTAgPC0gc3Vic2V0KGhvb2RfbWV0cmljcywgaG9vZF9tZXRyaWNzJEFDU18yMDEzXzIwMTcgPiAxNTApCmhvb2RfbWV0cmljc19vdmVyMTUwIDwtIHN1YnNldChob29kX21ldHJpY3Nfb3ZlcjE1MCwgIWdyZXBsKCJJTkRVU1RSSUFMIiwgbGFiZWwsIGlnbm9yZS5jYXNlID0gVCkpCmBgYAoKTnVtYmVyIG9mIG5laWdoYm9yaG9vZHMgd2l0aCBhdCBsZWFzdCBvbmUgaG9taWNpZGUgb3Igc2hvb3Rpbmc6CgpgYGB7cn0KaG9vZF9tZXRyaWNzX292ZXIxNTBAZGF0YSAlPiUKICBjb3VudChob21pY2lkZV9zaG9vdGluZ3MgPj0gMSkKYGBgCgpXaWxsIGxpa2VseSBhc3NpZ24gMSB0byBuZWlnaGJvcmhvb2RzIHdpdGggYXQgbGVhc3QgMSBob21pY2lkZSBvciBzaG9vdGluZywgMCB0byBhbGwgb3RoZXJzLiBUaGlzIHdpbGwgZW5zdXJlIG5laWdoYm9yaG9vZHMgd2l0aCBndW4gdmlvbGVuY2UgYXJlIGdpdmVuIHByZWNlZGVuY2UuCgojIEJ1aWxkaW5nIFRoZSBJbmRleAoKQWxsIHBhcmFtZXRlcnMgd2lsbCBiZSBzY2FsZWQgdG8gYmV0d2VlbiAwIGFuZCAxIGJlY2F1c2UgdmFyaWFibGVzIGFyZSBvbiBkaWZmZXJlbnQgc2NhbGVzLgoKYGBge3J9CiMgc2NhbGUgcGVyIGNhcGl0YQpzY2FsZV9pbmRleCA8LSBmdW5jdGlvbih4KXtyZXNjYWxlKHgsIHRvID0gYygwLDEpKX0KCmhvb2RfbWV0cmljc19vdmVyMTUwQGRhdGEgPC0gaG9vZF9tZXRyaWNzX292ZXIxNTBAZGF0YSAlPiUKICBtdXRhdGUoCiAgICBob21pY2lkZV9zaG9vdGluZ3NfaW5kZXggPSBpZmVsc2UoaG9taWNpZGVfc2hvb3RpbmdzID4gMCwgMSwgMCksCiAgICByYXBlX2luZGV4ID0gaWZlbHNlKHJhcGUgPiAwLCAxLCAwKSkgJT4lCiAgbXV0YXRlX2F0KAogICAgdmFycygKICAgICAgaWxsZWdhbF9kdW1waW5nX3Blcl8xMDAwX3JlcywKICAgICAgcHJvcGVydHlfbWFpbnRlbmFuY2VfcGVyXzEwMDBfcmVzLAogICAgICByaWdodF9vZl93YXlfcGVyXzEwMDBfcmVzLAogICAgICBob21pY2lkZV9zaG9vdGluZ3NfcGVyXzEwMDBfcmVzLAogICAgICByb2JiZXJ5X3Blcl8xMDAwX3JlcywKICAgICAgYWdnX2Fzc2F1bHRfcGVyXzEwMDBfcmVzLAogICAgICByYXBlX3Blcl8xMDAwX3JlcyksCiAgICBsaXN0KGluZGV4ID0gc2NhbGVfaW5kZXgpCiAgICApIApgYGAKCmBgYHtyfQojIHBsb3QgcmVzY2FsZWQgZGF0YQpob29kX21ldHJpY3Nfb3ZlcjE1MEBkYXRhICU+JQogIHNlbGVjdCgKICAgIGxhYmVsLAogICAgaWxsZWdhbF9kdW1waW5nX3Blcl8xMDAwX3Jlc19pbmRleCwKICAgIHByb3BlcnR5X21haW50ZW5hbmNlX3Blcl8xMDAwX3Jlc19pbmRleCwKICAgIHJpZ2h0X29mX3dheV9wZXJfMTAwMF9yZXNfaW5kZXgsCiAgICBob21pY2lkZV9zaG9vdGluZ3NfcGVyXzEwMDBfcmVzX2luZGV4LAogICAgcm9iYmVyeV9wZXJfMTAwMF9yZXNfaW5kZXgsCiAgICBhZ2dfYXNzYXVsdF9wZXJfMTAwMF9yZXNfaW5kZXgsCiAgICByYXBlX3Blcl8xMDAwX3Jlc19pbmRleAogICkgJT4lCiAgcGl2b3RfbG9uZ2VyKC1sYWJlbCkgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSkpICsKICBmYWNldF93cmFwKH5uYW1lLCBzY2FsZXMgPSAiZnJlZSIpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNTApICsKICAgIHRoZW1lX2l0ZWFtX2dvb2dsZV9kb2NzKCkgKwogIGxhYnModGl0bGUgPSAiU3RhbmRhcmRpemVkIChiZXR3ZWVuIDAgYW5kIDEpIDIwMTkgRXZlbnRzIHBlciAxLDAwMCBSZXNpZGVudHNcbk5laWdoYm9yaG9vZHMgd2l0aCA+PSAxNTAgUmVzaWRlbnRzIikKYGBgCgoqKkFnYWluLCBhbGwgbWV0cmljcyBoYXZlIGJlZW4gc2NhbGVkIHRvIGJldHdlZW4gMCBhbmQgMS4qKiBDb21iaW5lIGNyaW1lIG1ldHJpY3MgaW50byBhIGhvbWljaWRlIGFuZCBzaG9vdGluZyBwYXJhbWV0ZXIgKDAgaWYgbm9uZSBpbiBuZWlnaGJvcmhvb2QsIDEgaWYgYXQgbGVhc3QgMSkgYW5kIGFuICJvdGhlciB2aW9sZW50IGNyaW1lIiBwYXJhbWV0ZXIgd2hlcmU6CgokJCBIb21pY2lkZVNob290aW5nc0luZGV4ID0gXGJlZ2lue2Nhc2VzfQoxICYgXHRleHR7aWYgJE5fe2hvbWljaWRlc30rTl97c2hvb3RpbmdzfSA+IDAkIH0gXFwgCjAgJiBcdGV4dHtvdGhlcndpc2V9IFxlbmR7Y2FzZXN9JCQKCiQkIE90aGVyVmlvbGVudENyaW1lSW5kZXggPSBcZnJhY3sxfXszfShSYXBlSW5kZXgrIEFnZ0Fzc2F1bHRJbmRleCArIFJvYmJlcnlJbmRleCkgJCQKCkNvbWJpbmluZyB0aGUgdHdvIGNyaW1lIGluZGljZXM6CgokJCBDcmltZUluZGV4ID0gXGZyYWN7MX17Mn0gKEhvbWljaWRlU2hvb3RpbmdzSW5kZXggKyBPdGhlclZpb2xlbnRDcmltZUluZGV4KSAkJApgYGB7cn0KIyBjcmVhdGUgY29tYmluZWQgaW5kZXgKaG9vZF9tZXRyaWNzX292ZXIxNTBAZGF0YSA8LSBob29kX21ldHJpY3Nfb3ZlcjE1MEBkYXRhICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoCiAgICBvdGhlcl92aW9sZW50X2NyaW1lX2luZGV4ID0gCiAgICAgIG1lYW4oCiAgICAgICAgYyhyYXBlX3Blcl8xMDAwX3Jlc19pbmRleCwKICAgICAgICAgIGFnZ19hc3NhdWx0X3Blcl8xMDAwX3Jlc19pbmRleCwgCiAgICAgICAgICByb2JiZXJ5X3Blcl8xMDAwX3Jlc19pbmRleCksIAogICAgICAgIG5hLnJtID0gVCksCiAgICBncmltZV9pbmRleCA9IG1lYW4oCiAgICAgIGMoaWxsZWdhbF9kdW1waW5nX3Blcl8xMDAwX3Jlc19pbmRleCwKICAgICAgICBwcm9wZXJ0eV9tYWludGVuYW5jZV9wZXJfMTAwMF9yZXNfaW5kZXgsCiAgICAgICAgcmlnaHRfb2Zfd2F5X3Blcl8xMDAwX3Jlc19pbmRleCksCiAgICAgIG5hLnJtID0gVCkKICApICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoY3JpbWVfaW5kZXggPSAwLjUgKiBob21pY2lkZV9zaG9vdGluZ3NfcGVyXzEwMDBfcmVzX2luZGV4ICsgCiAgICAgICAgICAgMC41ICogb3RoZXJfdmlvbGVudF9jcmltZV9pbmRleCwKICAgICAgICAgY3JpbWVfZ3JpbWVfaW5kZXggPSAoMi8zKSAqIGNyaW1lX2luZGV4ICsgKDEvMykgKiBncmltZV9pbmRleCwKICAgICAgICAgY3JpbWVfZ3JpbWVfaW5kZXhfcXVhZHJhdHVyZSA9IHNxcnQoY3JpbWVfaW5kZXheMiArIGdyaW1lX2luZGV4XjIpKQpgYGAKCk5vdyBjcmVhdGluZyB0aGUgImdyaW1lIiBpbmRleDoKCiQkIEdyaW1lSW5kZXggPSBcZnJhY3sxfXszfShJbGxlZ2FsRHVtcGluZ0luZGV4ICsgUHJvcE1haW50ZW5hbmNlSW5kZXggKyBSaWdodE9mV2F5SW5kZXgpICQkCkNvbWJpbmUgaW50byBhIHNpbmdsZSBpbmRleC4gQ3JpbWUgd2VpZ2h0ZWQgbW9yZSBoZWF2aWx5LgoKJCQgQ3JpbWVHcmltZUluZGV4ID0gXGZyYWN7Mn17M31DcmltZUluZGV4ICsgXGZyYWN7MX17M31HcmltZUluZGV4ICQkCgojIEp1c3QgU2hvdyBNZSBUaGUgUmVzdWx0cwoKYGBge3IgZmlnLmhlaWdodCA9IDgsIGZpZy53aWR0aCA9IDh9CiMgYmFzZV9tYXAgPC0gZ2V0X21hcCgKIyAgIGxvY2F0aW9uPWMobG9uID0gLTc2LjYxLCBsYXQgPSAzOS4zKSwgCiMgICB6b29tPTEyLCAKIyAgIG1hcHR5cGUgPSAndG9uZXItbGl0ZScsIAojICAgc291cmNlID0gJ3N0YW1lbicKIyAgICkKIyAKIyBnZ21hcChiYXNlX21hcCkgKwojICAgIyBnZW9tX3BvaW50KAojICAgIyAgIGRhdGEgPSByZXBlYXRfb2ZmZW5kZXJfbG9jYXRpb25zICU+JQojICAgIyAgICAgZmlsdGVyKCFpcy5uYShsb24pLAojICAgIyAgICAgICAgICAgIG4gPj0gMyksCiMgICAjICAgYWVzKHggPSBsb24sIHkgPSBsYXQpLAojICAgIyAgIGNvbG9yID0gImJsdWUiLAojICAgIyAgIHNpemUgPSAyCiMgICAjICkgIAojICAgZ2VvbV9wb2x5Z29uKAojICAgICBkYXRhID0gZm9ydGlmeShob29kX21ldHJpY3Nfb3ZlcjE1MCkgJT4lCiMgICAgICAgZmlsdGVyKCFpcy5uYShjcmltZV9ncmltZV9pbmRleCkpLAojICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSBjcmltZV9ncmltZV9pbmRleCksIAojICAgICBhbHBoYSA9IDAuOSxjb2xvciA9ICJibGFjayIKIyAgICkjICsKIyAgICMgc2NhbGVfZmlsbF92aXJpZGlzX2MobmEudmFsdWUgPSBOQSwgCiMgICAjICAgICAgICAgICAgICAgICAgICAgICN2YWx1ZXMgPSByZXNjYWxlKGMoMCwgMjAsIDcwKSwgYygwLDEpKSwKIyAgICMgICAgICAgICAgICAgICAgICAgICAgb3B0aW9uID0gInBsYXNtYSIsCiMgICAjICAgICAgICAgICAgICAgICAgICAgIGd1aWRlX2NvbG9yYmFyKHRpdGxlID0gIkFubnVhbCBEViBJbmNpZGVudHNcbnBlciAxLDAwMCBSZXNpZGVudHMiKSkKIyAgICMgCmBgYAoKYGBge3J9Cmhvb2RzQGRhdGEgPC0gaG9vZHNAZGF0YSAlPiUKICBsZWZ0X2pvaW4oCiAgICBob29kX21ldHJpY3Nfb3ZlcjE1MEBkYXRhICU+JSBzZWxlY3QoLUFDU18yMDEzXzIwMTcpLAogICAgYnkgPSAibGFiZWwiCiAgKQpgYGAKCgpgYGB7cn0KIyBjb2xvciBwYWxldHRlIGZvciBtYXAKcGFsIDwtICBjb2xvck51bWVyaWMoInZpcmlkaXMiLCAKICAgICAgICAgICAgICAgICAgICAgZG9tYWluID0gaG9vZHMkY3JpbWVfZ3JpbWVfaW5kZXgsCiAgICAgICAgICAgICAgICAgICAgIG5hLmNvbG9yID0gInRyYW5zcGFyZW50IikKCiMgbGFiZWxzCmhvb2RzQGRhdGEgPC0gaG9vZHNAZGF0YSAlPiUKICBtdXRhdGUobWFwX2xhYmVsID0gcGFzdGUwKAogICAgIjxiPiIsaG9vZHMkbGFiZWwsICI8L2I+PGJyPiIsCiAgICAiUG9wdWxhdGlvbjogIiwgcm91bmQoaG9vZHMkQUNTXzIwMTNfMjAxNywgMCksICI8YnI+IiwKICAgICJDcmltZSAmIEdyaW1lIEluZGV4OiAiLCByb3VuZChob29kcyRjcmltZV9ncmltZV9pbmRleCwgMiksICI8YnI+IiwKICAgICJDcmltZSBJbmRleDogIiwgcm91bmQoaG9vZHMkY3JpbWVfaW5kZXgsIDIpLCAiPGJyPiIsCiAgICAiR3JpbWUgSW5kZXg6ICIsIHJvdW5kKGhvb2RzJGdyaW1lX2luZGV4LCAyKSwgIjxicj4iLAogICAgIjxicj48Yj4yMDE5IENyaW1lPC9iPjxicj4iLAogICAgIkhvbWljaWRlcyArIFNob290aW5nczogIiwgaG9vZHMkaG9taWNpZGVfc2hvb3RpbmdzLCAiPGJyPiIsCiAgICAiUmFwZTogIiwgaG9vZHMkcmFwZSwgIjxicj4iLAogICAgIkFnZ3JhdmF0ZWQgQXNzYXVsdDogIiwgaG9vZHMkYWdnX2Fzc2F1bHQsICI8YnI+IiwKICAgICJSb2JiZXJ5OiAiLCBob29kcyRyb2JiZXJ5LCAiPGJyPiIsCiAgICAiPGJyPjxiPjIwMTkgU2VydmljZSBSZXF1ZXN0czwvYj48YnI+IiwKICAgICJJbGxlZ2FsIER1bXBpbmcgU1JzOiAiLCBob29kcyRpbGxlZ2FsX2R1bXBpbmcsICI8YnI+IiwKICAgICJQcm9wZXJ0eSBNYWludGVuYW5jZSBTUnM6ICIsIGhvb2RzJHByb3BlcnR5X21haW50ZW5hbmNlLCAiPGJyPiIsCiAgICAiUmlnaHQtb2YtV2F5IFNSczogIiwgaG9vZHMkcmlnaHRfb2Zfd2F5LCAiPGJyPiIKKSkKYGBgCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNn0KbGVhZmxldCgpICU+JQogIHNldFZpZXcobG5nID0gLTc2LjYsIGxhdCA9IDM5LjMsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGl0ZSkgJT4lIAogIGFkZFBvbHlnb25zKGRhdGEgPSBob29kcywKICAgICAgICAgICAgICB3ZWlnaHQgPSAyLAogICAgICAgICAgICAgIGZpbGxDb2xvciAgPSB+cGFsKGhvb2RzJGNyaW1lX2dyaW1lX2luZGV4KSwKICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuOSwKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNzUsCiAgICAgICAgICAgICAgbGFiZWwgPSBob29kcyRsYWJlbCwKICAgICAgICAgICAgICBwb3B1cCA9IH5sYXBwbHkoaG9vZHMkbWFwX2xhYmVsLCBIVE1MKSwKICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICN0ZXh0T25seSA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdGlvbiA9ICdyaWdodCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHRzaXplID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDEpKSAlPiUKICBhZGRDb250cm9sKCJCYWx0aW1vcmU8YnI+Q3JpbWUgJiBHcmltZSBJbmRleCIsIHBvc2l0aW9uID0gInRvcGxlZnQiKQpgYGAKCiMjIERpc3RyaWJ1dGlvbiBvZiBJbmRleGVzCgpCZWNhdXNlIG9mIHRoZSBob21pY2lkZSBhbmQgc2hvb3RpbmcgaW5kZXggYmVpbmcgMSBmb3IgbmVpZ2hib3Job29kcyB3aXRoIGF0IGxlYXN0IG9uZSBob21pY2lkZSBvciBzaG9vdGluZyBhbmQgMCBmb3IgYWxsIG90aGVycywgdGhlIGRpc3RyaWJ1dGlvbiBpcyBiaWZ1cmNhdGVkIGludG8gbmVpZ2hib3Job29kcyB3aXRoIGFuZCB3aXRob3V0IHNob290aW5ncy4KCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMSwgZmlnLndpZHRoID0gMn0KaG9vZHNAZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKGNyaW1lX2dyaW1lX2luZGV4KSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIHRoZW1lX2l0ZWFtX2dvb2dsZV9kb2NzKCkgCmBgYAoKIyMgVGhlIExpc3QsIEluIE9yZGVyIG9mIERlc2NlbmRpbmcgQ3JpbWUtR3JpbWUgSW5kZXgKClNvbWUgbmVpZ2hib3Job29kcyB3aXRoIHZlcnkgc21hbGwgcG9wdWxhdGlvbnMgc3RpbGwgc25lYWsgb250byB0aGUgbGlzdCBiZWNhdXNlIHdlIG9ubHkgdXNlZCAxNTAgYXMgdGhlIGN1dC1vZmYsIGJ1dCB0aG9zZSBjYW4gYmUgbWFudWFsbHkgZHJvcHBlZCBpZiBub3QgaW50ZXJlc3RlZC4KCmBgYHtyfQp0aGVfbGlzdCA8LSBob29kc0BkYXRhICU+JQogIGFycmFuZ2UoZGVzYyhjcmltZV9ncmltZV9pbmRleCkpICU+JQogIHNlbGVjdCgtYWNyZXMsIC1uYnJkZXNjLCAtc2hhcGVfbGVuZywgLXNoYXBlX2FyZWEsIC1jb2xvcl8yLCAtRGVjZW5uaWFsMjAxMCkgJT4lCiAgcmVuYW1lKG5laWdoYm9yaG9vZCA9IGxhYmVsLAogICAgICAgICBwb3B1bGF0aW9uX2Fjc18yMDEzXzIwMTcgPSBBQ1NfMjAxM18yMDE3KQoKd3JpdGVfY3N2KHRoZV9saXN0LCAiLi4vZGF0YS9wcm9jZXNzZWQvY3JpbWVfZ3JpbWVfaW5kZXhfdjMuY3N2IikKCnRoZV9saXN0CmBgYAoKYGBge3J9CnRvcF8yMCA8LSBob29kcyAlPiUKICBhcnJhbmdlKGRlc2MoY3JpbWVfZ3JpbWVfaW5kZXgpKSAlPiUKICB0b3BfbihuID0gMjAsIHd0ID0gY3JpbWVfZ3JpbWVfaW5kZXgpCmBgYAoKYGBge3J9CmNvbG9yX2xpc3QgPC0gaG9vZHNAZGF0YSAlPiUKICBhcnJhbmdlKGRlc2MoY3JpbWVfZ3JpbWVfaW5kZXgpKSAlPiUKICBtdXRhdGUodG9wMjBfY29sb3IgPSBpZmVsc2Uocm93X251bWJlcigpIDw9IDIwLCBpdGVhbS5jb2xvcnNbMV0sIE5BKSkgJT4lCiAgc2VsZWN0KGxhYmVsLCB0b3AyMF9jb2xvcikKCmhvb2RzQGRhdGEgPC0gaG9vZHNAZGF0YSAlPiUKICBsZWZ0X2pvaW4oY29sb3JfbGlzdCwgYnkgPSAibGFiZWwiKQpgYGAKCiMgVG9wIDIwIE5laWdoYm9yaG9vZHMKCmBgYHtyIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA2fQpmaWxsX3BhbCA8LSBjb2xvckZhY3Rvcihob29kcyR0b3AyMF9jb2xvciwgaG9vZHMkdG9wMjBfY29sb3IsIG5hLmNvbG9yID0gInRyYW5zcGFyZW50IikKCmxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxuZyA9IC03Ni42LCBsYXQgPSAzOS4zLCB6b29tID0gMTEpICU+JQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxpdGUpICU+JSAKICBhZGRQb2x5Z29ucyhkYXRhID0gaG9vZHMsCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICBmaWxsQ29sb3IgID0gfmZpbGxfcGFsKGhvb2RzJHRvcDIwX2NvbG9yKSwKICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuOSwKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNzUsCiAgICAgICAgICAgICAgbGFiZWwgPSBob29kcyRsYWJlbCwKICAgICAgICAgICAgICBwb3B1cCA9IH5sYXBwbHkoaG9vZHMkbWFwX2xhYmVsLCBIVE1MKSwKICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICN0ZXh0T25seSA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdGlvbiA9ICdyaWdodCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHRzaXplID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDEpKSAlPiUKICBhZGRDb250cm9sKCJCYWx0aW1vcmU8YnI+Q3JpbWUgJiBHcmltZSBJbmRleDxicj5Ub3AgMjAgTmVpZ2hib3Job29kczxicj48YnI+Q2xpY2sgTmVpZ2hib3Job29kPGJyPkZvciBNb3JlIEluZm8iLCAKICAgICAgICAgICAgIHBvc2l0aW9uID0gInRvcGxlZnQiKQpgYGAKCiMgQ3JpbWUgdi4gR3JpbWUKUmVkIG1lYW5zIHRoZSBjcmltZSBpbmRleCB3YXMgaGlnaGVyLCBibHVlIG1lYW5zIHRoZSBncmltZSBpbmRleCB3YXMgaGlnaGVyLgoKYGBge3J9Cmhvb2RzQGRhdGEgPC0gaG9vZHNAZGF0YSAlPiUgbXV0YXRlKGNyaW1lX2dyaW1lX2luZGV4X2RpZmYgPSBjcmltZV9pbmRleCAtIGdyaW1lX2luZGV4KQpgYGAKCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNn0KZGlmZl9wYWwgPC0gIGNvbG9yTnVtZXJpYygiUmRCdSIsIAogICAgICAgICAgICAgICAgICAgICBkb21haW4gPSBob29kcyRjcmltZV9ncmltZV9pbmRleF9kaWZmLAogICAgICAgICAgICAgICAgICAgICBuYS5jb2xvciA9ICJ0cmFuc3BhcmVudCIsIHJldmVyc2UgPSBUKQoKbGVhZmxldCgpICU+JQogIHNldFZpZXcobG5nID0gLTc2LjYsIGxhdCA9IDM5LjMsIHpvb20gPSAxMSkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLlRvbmVyTGl0ZSkgJT4lIAogIGFkZFBvbHlnb25zKGRhdGEgPSBob29kcywKICAgICAgICAgICAgICB3ZWlnaHQgPSAyLAogICAgICAgICAgICAgIGZpbGxDb2xvciAgPSB+ZGlmZl9wYWwoaG9vZHMkY3JpbWVfZ3JpbWVfaW5kZXhfZGlmZiksCiAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjksCiAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjc1LAogICAgICAgICAgICAgIGxhYmVsID0gaG9vZHMkbGFiZWwsCiAgICAgICAgICAgICAgcG9wdXAgPSB+bGFwcGx5KGhvb2RzJG1hcF9sYWJlbCwgSFRNTCksCiAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IEYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjdGV4dE9ubHkgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAncmlnaHQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0c2l6ZSA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wYWNpdHkgPSAxKSkgJT4lCiAgYWRkQ29udHJvbCgiQmFsdGltb3JlPGJyPkNyaW1lICYgR3JpbWUgSW5kZXg8YnI+Q3JpbWUtR3JpbWUgRGlmZmVyZW5jZTxicj48YnI+Q2xpY2sgTmVpZ2hib3Job29kPGJyPkZvciBNb3JlIEluZm8iLCAKICAgICAgICAgICAgIHBvc2l0aW9uID0gInRvcGxlZnQiKSAlPiUKICBhZGRMZWdlbmQocGFsID0gZGlmZl9wYWwsIHZhbHVlcyA9IGhvb2RzJGNyaW1lX2dyaW1lX2luZGV4X2RpZmYsIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0IiwgdGl0bGUgPSAiUmVkOiBNb3JlIENyaW1lPGJyPkJsdWU6IE1vcmUgR3JpbWUiKQpgYGA=